/* dnsbl.c
 * Incorporate some of the functions of BOPM into ratbox-monitor
 * Based on BOPM by Erik Fears
 * http://wiki.blitzed.org/BOPM
 *
 * Copyright (C) 2002-2003  Erik Fears
 * Copyright (C) 2011-2012 Dan Reidy <dubkat@ratbox.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 *
 *       Foundation, Inc.
 *       59 Temple Place - Suite 330
 *       Boston, MA  02111-1307, USA.
 *
 *
 * $Id: dnsbl.c 21 2012-03-26 03:59:17Z dubkat $
 *
 */

#include <bits/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>

#include "setup.h"
#include "ratbox_lib.h"
#include "libconfig.h"
#include "common.h"
#include "worker.h"
#include "dnsbl.h"
#include "ares.h"
#include "logger.h"
#include "openmp.h"



extern config_t conf;
extern ares_channel dns;

void check_dnsbl(struct conn *, struct my_clients *);
static void dns_loop(ares_channel);
static void dns_callback(void *, int, int, struct hostent *);
int get_bantype(const char *);

int dnsbl_ban_client(struct conn *serv, struct my_clients *cli, int type, int time,
		     const char *msg);

struct cb_struct
{
	struct conn *connection;
	struct my_clients *client;
	char listname[128];
	config_setting_t *listinfo;
};

void
check_dnsbl(struct conn *server, struct my_clients *client)
{
	config_setting_t *blacklist;
	config_setting_t *each;
	struct in_addr in;

	struct cb_struct cbstruct;

	unsigned char a, b, c, d;
	char reverse[16];
	char blackname[128];
	char lookup[256];


	unsigned int i;

	if(client->isSpoofed || client->isBanned)
		return;

	/* might as well skip over ipv6 */
	if(strchr(client->ip, ':'))
		return;

	/* ripped right from bopm */
	if(!inet_aton(client->ip, &in))
	{
		logger(DEBUG, "DNSBL Error: Invalid address '%s', ignoring.", client->ip);
		return;
	}

	/* pu kcuf eht kcab
	 * nice trick to reverse an ip, ripped from BOPM
	 */

	d = (unsigned char)(in.s_addr >> 24) & 0xFF;
	c = (unsigned char)(in.s_addr >> 16) & 0xFF;
	b = (unsigned char)(in.s_addr >> 8) & 0xFF;
	a = (unsigned char)in.s_addr & 0xFF;

#ifdef WORDS_BIGENDIAN
	rb_snprintf(reverse, 16, "%d.%d.%d.%d", a, b, c, d);
#else
	rb_snprintf(reverse, 16, "%d.%d.%d.%d", d, c, b, a);
#endif

	// blacklists are a "list"
	blacklist = config_lookup(&conf, "dnsbl");

	cbstruct.connection = server;
	cbstruct.client = client;

	for(i = 0; i < config_setting_length(blacklist); i++)
	{
		each = config_setting_get_elem(blacklist, i);
		cbstruct.listinfo = each;
		rb_snprintf(blackname, 128, "%s",
			    config_setting_get_string(config_setting_get_member(each, "name")));

		rb_snprintf(lookup, 256, "%s.%s", reverse, blackname);
		rb_snprintf(cbstruct.listname, sizeof(cbstruct.listname), "%s", blackname);


		logger(DEBUG, "Checking client ip: %s against blacklist: %s (%s)",
		       client->ip, blackname, lookup);

		ares_gethostbyname(dns, lookup, AF_INET, dns_callback, &cbstruct);
	}

#	pragma omp parallel
	{
		logger(INFO, "Threads: (check_dnsbl) I am thread %d out of %d.",
		       omp_get_thread_num(), omp_get_num_threads());
		dns_loop(dns);
	}
	//rb_free(&cbstruct);
}

static void
dns_loop(ares_channel channel)
{
	int nfds, count;
	fd_set readers, writers;
	struct timeval tv, *tvp;

	do
	{
		FD_ZERO(&readers);
		FD_ZERO(&writers);
		nfds = ares_fds(channel, &readers, &writers);
		if(nfds == 0)
			break;
		tvp = ares_timeout(channel, NULL, &tv);
		count = select(nfds, &readers, &writers, NULL, tvp);
		ares_process(channel, &readers, &writers);
	}
	while(1);

}

static void
dns_callback(void *arg, int status, int timeouts, struct hostent *host)
{
	struct cb_struct *s = arg;
	struct conn *serv = s->connection;
	struct my_clients *client = s->client;

	struct in_addr in;
	unsigned char mask;
	char ip[16];
	char banmsg[255];
	char output[IRCD_BUFSIZE];
	int bantime;
	int bt = INVALID;

	/* nothing to report if this was automated */
	if(*client->host != '#' && *client->host != '&')
		if(!host || status != ARES_SUCCESS)
			return;

	/* for right now, only ban users if rbl is set to ban all unknowns */
	if(!config_setting_get_bool(config_setting_get_member(s->listinfo, "ban_unknown")))
		return;

	rb_snprintf(banmsg, sizeof(banmsg), "Unknown");

	if(status == ARES_SUCCESS)
	{
		/* "127.0.0.1" */
		inet_ntop(host->h_addrtype, host->h_addr_list[0], ip, sizeof(ip));
		logger(DEBUG, "DNS: Got hostname: %s -> %s", host->h_name, ip);

		/* 127.0.0.[1] */
		if(!inet_aton(ip, &in))
			return;

		mask = (unsigned char)in.s_addr & 0xFF;

		/* client->host is being used to hold the channel name on
		 * manually requested rbl lookups
		 */
		if(*client->host == '#' || *client->host == '&')
		{
			rb_snprintf(output, IRCD_BUFSIZE,
				    "PRIVMSG %s :DNSBL -> %s appears in BL zone %s (%d)",
				    client->host, client->ip, s->listname, mask);

			sendto_one(serv, output);
			rb_free(client);	// allocated in worker.c
			return;
		}

		bantime =
			config_setting_get_int(config_setting_get_member(s->listinfo, "ban_time"));
		bt = get_bantype(config_setting_get_string
				 (config_setting_get_member(s->listinfo, "ban_type")));

		logger(DEBUG, "bantype is %d", bt);

		rb_snprintf(banmsg, sizeof(banmsg), "%s",
			    config_setting_get_string(config_setting_get_member
						      (s->listinfo, "ban_message")));

		dnsbl_ban_client(serv, client, bt, bantime, banmsg);

		report_to_channel(serv,
				  "DNSBL -> %s!%s@%s appears in BL zone %s (%d)",
				  client->nick, client->user, client->host, s->listname, mask);
		return;
	}

	rb_snprintf(output, IRCD_BUFSIZE, "PRIVMSG %s :DNSBL -> %s does not appear in BL zone %s",
		    client->host, client->ip, s->listname);

	sendto_one(serv, output);

}

/**
 * Ban the client from the server
 * @input connection struct, client struct, ban type
 * @return none
 */
int
dnsbl_ban_client(struct conn *serv, struct my_clients *cli, int type, int bantime, const char *msg)
{
	char putserv[IRCD_BUFSIZE];

	// they are already banned or spoofed, nothing to for us to do.
	if(cli->isBanned || cli->isSpoofed)
		return NO;

	if(type == INVALID)
		return NO;

	switch (type)
	{
	case KILL:
		rb_snprintf(putserv, IRCD_BUFSIZE, "KILL %s %s", cli->nick, msg);
		break;

	case XLINE:
		logger(INFO, "WARNING: XLINE is not supported yet... K-Lining instead.");
		// fall-though to kline

	case KLINE:
		rb_snprintf(putserv, IRCD_BUFSIZE, "KLINE %i %s@%s :%s",
			    bantime, (*(cli->user) == '~') ? "*" : cli->user, cli->ip, msg);
		break;

	case DLINE:
		rb_snprintf(putserv, IRCD_BUFSIZE, "DLINE %i %s :%s", bantime, cli->ip, msg);
		break;

	default:		// shouldn't get here.
		break;

	}
	cli->isBanned = YES;
	sendto_one(serv, putserv);
	logger(INFO, putserv);

	return YES;

}
